package org.example.util;

import org.reflections.Reflections;
import org.reflections.scanners.FieldAnnotationsScanner;
import org.reflections.scanners.MethodAnnotationsScanner;
import org.reflections.scanners.MethodParameterScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ConfigurationBuilder;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public abstract class AbstractAnnotationUtil<C> implements AnnotationUtil<C> {

    private List<Class<?>> classes;

    private Reflections reflections;

    @Override
    public AnnotationUtil<C> init(C context) throws Throwable {
        List<String> ps = new ArrayList<>();
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        Class<? extends ClassLoader> aClass = classLoader.getClass();
        Method packages = null;
        Class<?> cache = aClass;
        while (packages == null) {
            try {
                packages = cache.getDeclaredMethod("getPackages");
            } catch (Exception e) {
                if (e instanceof NullPointerException) {
                    break;
                } else {
                    cache = cache.getSuperclass();
                }
            }
        }
        if (packages == null) {
            throw new IllegalStateException("初始化异常");
        }
        packages.setAccessible(true);
        Object invoke = packages.invoke(classLoader);
        if (invoke instanceof Package[]) {
            for (Package p : (Package[]) invoke) {
                String name = p.getName();
                ps.add(name);
            }
        }
        packages.setAccessible(false);
        if (!ps.isEmpty()) {
            reflections = new Reflections(new ConfigurationBuilder().forPackages(ps.toArray(new String[0]))
                    .addScanners(new SubTypesScanner())
                    .addScanners(new FieldAnnotationsScanner())
                    .addScanners(new MethodAnnotationsScanner())
                    .addScanners(new MethodParameterScanner()));
        }
        return this;
    }

    @Override
    public List<Class<?>> getClassByAnnotation(Class<? extends Annotation> cls) {
        List<Class<?>> list = new ArrayList<>();
        classes.forEach(aClass -> {
            Annotation annotation = aClass.getDeclaredAnnotation(cls);
            if (annotation == null) {
                return;
            }
            list.add(aClass);
        });
        return list;
    }

    @Override
    public List<Method> getMethodByAnnotation(Class<?> oClass, Class<? extends Annotation> mClass) {
        List<Method> list = new ArrayList<>();
        Method[] methods = oClass.getDeclaredMethods();
        for (Method method : methods) {
            Annotation annotation = method.getDeclaredAnnotation(mClass);
            if (annotation == null) {
                continue;
            }
            list.add(method);
        }
        return list;
    }

    @Override
    public List<Field> getFiledByAnnotation(Class<?> oClass, Class<? extends Annotation> fClass) {
        List<Field> list = new ArrayList<>();
        Field[] fields = oClass.getDeclaredFields();
        for (Field field : fields) {
            Annotation annotation = field.getDeclaredAnnotation(fClass);
            if (annotation == null) {
                continue;
            }
            list.add(field);
        }
        return list;
    }

    @Override
    public List<Parameter> getParameterByAnnotation(Class<?> oClass, Class<? extends Annotation> pClass) {
        List<Parameter> list = new ArrayList<>();
        Method[] methods = oClass.getDeclaredMethods();
        for (Method method : methods) {
            List<Parameter> parameters = getParameterByAnnotationMethod(method, pClass);
            list.addAll(parameters);
        }
        return list;
    }

    @Override
    public List<Parameter> getParameterByAnnotationMethod(Method method, Class<? extends Annotation> pClass) {
        List<Parameter> list = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (Parameter parameter : parameters) {
            Annotation annotation = parameter.getDeclaredAnnotation(pClass);
            if (annotation == null) {
                list.add(parameter);
            }
        }
        return list;
    }


    /**
     * 获取拥有指定注解的类的Class对象
     *
     * @param cls 指定的注解的Class对象
     *            注意此注解不能被子类继承
     * @return 拥有cls注解的所有类的类对象的Set集合
     */
    public Set<Class<?>> getAnnotationByClass(Class<? extends Annotation> cls) {
        return reflections.getTypesAnnotatedWith(cls, true);
    }

    /**
     * 返回拥有指定注解的类的Class对象
     *
     * @param cls            指定注解的Class对象
     *                       注意：
     *                       若honorInherited变量为true，则注解不能被子类继承
     *                       若honorInherited变量为false，则注解可以被子类继承
     * @param honorInherited 此变量表示子类是否拥有cls注解 true时表示，子类不拥有cls注解，为false时反之
     * @return 拥有cls注解的类的类对象的Set集合
     */
    public Set<Class<?>> getAnnotationByClass(Class<? extends Annotation> cls, boolean honorInherited) {
        return reflections.getTypesAnnotatedWith(cls, honorInherited);
    }

    /**
     * 返回拥有指定注解的成员变量的Field变量的Set集合
     *
     * @param cls 指定注解的Class对象
     * @return 返回拥有cls注解的所有成员变量的Set集合
     */
    public Set<Field> getAnnotationByField(Class<? extends Annotation> cls) {
        return reflections.getFieldsAnnotatedWith(cls);
    }

    /**
     * 获取拥有指定注解的Method对象的Set集合
     *
     * @param cls 指定注解的Class对象
     * @return 拥有cls注解的Method的Set集合
     */
    public Set<Method> getAnnotationByMethod(Class<? extends Annotation> cls) {
        return reflections.getMethodsAnnotatedWith(cls);
    }
}
